package org.allen.utils;

import org.allen.annotation.FieldAutowired;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;

/**
 * @Author: Allen
 * @Date: Created in 16:29 2019-09-26
 * @Description:
 */
@Component
public class FieldAutowiredUtils implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void autoWired(Object object) throws Exception {
        if (object instanceof Collection) {
            autoWiredCollection((Collection) object);
        }else
            autoWiredSingle(object);
    }

    public void autoWiredCollection(Collection collection) throws Exception {
        for (Iterable iterable = (Iterable) collection.iterator(); ((Iterator) iterable).hasNext(); ) {
            Object next = ((Iterator) iterable).next();
            autoWiredSingle(next);
        }
    }

    public void autoWiredSingle(Object object) throws Exception {
        Class<?> thisObj = object.getClass();

        for (Field field : thisObj.getDeclaredFields()) {
            Annotation thisFieldAnnotation = field.getDeclaredAnnotation(FieldAutowired.class);
            if (thisFieldAnnotation == null) continue;
            field.setAccessible(true);
            Class<?> targetClass = ((FieldAutowired) thisFieldAnnotation).targetClass();
            String targetMethodName = ((FieldAutowired) thisFieldAnnotation).targetMethod();
            String targetFieldName = ((FieldAutowired) thisFieldAnnotation).targetField();
            String thisFieldForParam = ((FieldAutowired) thisFieldAnnotation).thisFieldForParam();

            //执行数据请求
            Object targetBean = applicationContext.getBean(targetClass);
            Method targetMethod = targetBean.getClass().getDeclaredMethod(targetMethodName, thisObj.getDeclaredField(thisFieldForParam).getType());
            Field thisField = thisObj.getDeclaredField(thisFieldForParam);
            thisField.setAccessible(true);

            Object invokeResult = targetMethod.invoke(targetBean, thisField.get(object));

            //这里需要判断返回值是数组还是对象，如果是数组应该报错，如果不存在数据也应该报错
            if (invokeResult == null || invokeResult instanceof Collection)
                throw new IllegalArgumentException(String.format("%s %s(%s) no such method be found" ,targetClass ,targetMethodName ,thisFieldForParam));

            //反射拿值
            Field targetField = invokeResult.getClass().getDeclaredField(targetFieldName);
            targetField.setAccessible(true);
            Object targetValue = targetField.get(invokeResult);

            //最后注入当前返回对象
            field.set(object ,targetValue);
            //完成
        }
    }

}
